#include <linux/io.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fh_simple_timer.h>
#include <linux/clk.h>
#include <chip.h>

//#define  FH_TIMER_DEBUG
#ifdef FH_TIMER_DEBUG
#define PRINT_DBG(fmt,args...)  printk(fmt,##args)
#else
#define PRINT_DBG(fmt,args...)  do{} while(0)
#endif

static unsigned int stmr_regbase = 0;
static unsigned int stmr_clkbase = 0;








enum SIMPLE_TIMER_WORKMODE {
	SIMPLE_TIMER_SEQ,
	SIMPLE_TIMER_PERIOD
};

struct simple_time_base
{
	struct timerqueue_head	simple_timer_queue;
	int state;
	int workmode;
};
struct fh_simple_timer periodic_timer;


static struct simple_time_base base;

static void fh_timer_enable(void)
{
		SET_REG(stmr_regbase+0x54,0x10003);
	if (base.workmode == SIMPLE_TIMER_SEQ) {
		SET_REG(stmr_regbase+0x50,0x10002);
	} else {
		SET_REG(stmr_regbase+0x50,0x10003);
	}
	SET_REG_M((stmr_regbase+0x54),0x1,0x1);	
}

static void fh_timer_disable(void)
{
	SET_REG(stmr_regbase+0x50,0);
	SET_REG((stmr_regbase+0x54),0x8);	
}

static void fh_timer_clearirq(void)
{
	SET_REG_M((stmr_regbase+0x54),0x8,0x8);

}

void fh_simple_timer_set_next(long cycles)
{
	int sync_cnt =  0;

	PRINT_DBG("cycles: %lu\n", cycles);
	cycles *= stmr_clkbase;

	if (cycles < 0) {
		pr_err("ERROR: cycles is invaild: %lu\n", cycles);
		fh_timer_clearirq();
		fh_timer_disable();
		base.state = SIMPLE_TIMER_ERROR;
		return;
	}

	fh_timer_disable();
	SET_REG(stmr_regbase+0x40, cycles);

    fh_timer_enable();
}

int fh_simple_timer_create(struct fh_simple_timer* new)
{
	timerqueue_init(&new->node);
	new->node.expires = new->it_value;
	timerqueue_add(&base.simple_timer_queue, &new->node);
	return 0;
}
EXPORT_SYMBOL_GPL(fh_simple_timer_create);

int fh_timer_start(void)
{
	struct fh_simple_timer *timer = NULL;
	struct timerqueue_node *node;

	if (base.state == SIMPLE_TIMER_START)
		return 0;


	node = timerqueue_getnext(&base.simple_timer_queue);

	if(node == NULL)
	{
		pr_err("ERROR: timequeue is empty\n");
		return -1;
	}
	base.workmode = SIMPLE_TIMER_SEQ;
	timer = container_of(node, struct fh_simple_timer, node);

	base.state = SIMPLE_TIMER_START;
	fh_timer_enable();
	fh_simple_timer_set_next(ktime_to_us(ktime_sub(timer->it_value, timer->it_delay)));
	return 0;
}
EXPORT_SYMBOL_GPL(fh_timer_start);

int fh_simple_timer_interrupt_seq(void)
{
	ktime_t diff;
	struct fh_simple_timer *curr = NULL, *next = NULL;
	struct timerqueue_node *node;

	node = timerqueue_getnext(&base.simple_timer_queue);

	if(node == NULL)
	{
		pr_err("ERROR: timequeue is empty\n");
		fh_timer_clearirq();
		fh_timer_disable();
		base.state = SIMPLE_TIMER_ERROR;
		return -1;
	}

	curr = container_of(node, struct fh_simple_timer, node);

	timerqueue_del(&base.simple_timer_queue, &curr->node);

	curr->function(curr->param);

	node = timerqueue_getnext(&base.simple_timer_queue);

	if(node == NULL)
	{
		PRINT_DBG("finished all timers, close device\n");
		fh_timer_clearirq();
		fh_timer_disable();
		base.state = SIMPLE_TIMER_STOP;
		return 0;
	}

	next = container_of(node, struct fh_simple_timer, node);

	PRINT_DBG("sec: %lu, nsec: %lu\n",
			ktime_to_timespec(next->it_value).tv_sec,
			ktime_to_timespec(next->it_value).tv_nsec);

	diff = ktime_sub(next->it_value, curr->it_value);

	fh_simple_timer_set_next(ktime_to_us(ktime_sub(diff, next->it_delay)));
	fh_timer_clearirq();
	return 0;
}
int fh_simple_timer_interrupt_period(void)
{

	periodic_timer.function(periodic_timer.param);
	fh_timer_clearirq();
	return 0;
}

int fh_simple_timer_interrupt(void)
{
	if (base.workmode == SIMPLE_TIMER_SEQ)
		return fh_simple_timer_interrupt_seq();
	else
		return fh_simple_timer_interrupt_period();
}



irqreturn_t mol_simpletimer_isr(int irq, void *dev_id)
{
    if ((GET_REG(stmr_regbase + 0x54) & 0x1 ) == 0x1) {
        fh_simple_timer_interrupt();
    }
	return IRQ_HANDLED;
}



void timertest_perodic_isr(void* p)
{
	struct timeval tv;
	do_gettimeofday(&tv);
	pr_err("cur time %lu %lu\n",tv.tv_sec,tv.tv_usec);
}



int __mol_simple_timer_init(unsigned int regbase,unsigned int clkbase,unsigned int hwirq)
{
	unsigned long irq = 0;
	int err = 0;
	base.state = SIMPLE_TIMER_STOP;
	stmr_regbase = ioremap(regbase,0x1000);
#ifdef CONFIG_FPGA
	clkbase = 36 * 1000000;
#endif
	if (clkbase % 1000000 != 0 || clkbase < 1000000) {
		pr_err("simple timer clk not support %u\n",clkbase);
		stmr_clkbase = 1;
	} else {
		stmr_clkbase = clkbase/1000000;
	}
	timerqueue_init_head(&base.simple_timer_queue);
	memset(&periodic_timer, 0, sizeof(periodic_timer));
	fh_timer_disable();
	irq = irq_create_mapping(NULL,hwirq);
	err = request_irq(irq, mol_simpletimer_isr, IRQF_TIMER,
		"mol-simple-timer", NULL);
	if (err != 0) {
		pr_err("mol-simple-timer request irq failed &x\n",err);
	} 
	
	return 0;
}


int fh_simple_timer_periodic_start(struct fh_simple_timer *tim)
{

	if (base.state == SIMPLE_TIMER_START)
		return 0;

	if (tim == NULL)
		return 0;

	periodic_timer = *tim;



	base.state = SIMPLE_TIMER_START;
	base.workmode = SIMPLE_TIMER_PERIOD;
	fh_timer_enable();
	fh_simple_timer_set_next(ktime_to_us(ktime_sub(periodic_timer.it_value,
		periodic_timer.it_delay)));

	return 0;
}
EXPORT_SYMBOL_GPL(fh_simple_timer_periodic_start);
int fh_simple_timer_periodic_stop(void)
{
	fh_timer_disable();
	base.state = SIMPLE_TIMER_STOP;

	return 0;
}
EXPORT_SYMBOL_GPL(fh_simple_timer_periodic_stop);








static int __init mol_simple_timer_init(void)
{
	struct clk *tmrclk = clk_get(NULL,"tmr0_clk");
	unsigned long clkrate = 0;
	if (IS_ERR(tmrclk)) {
		pr_err("simple timer clk not exist \n");
		clkrate = 36*1000000;
	} else {
		clkrate = clk_get_rate(tmrclk);
		clk_prepare_enable(tmrclk);
	}
	__mol_simple_timer_init(TIMER0_REG_BASE,clkrate,TMR0_IRQ);
    return 0;
}

static void __exit mol_simple_timer_exit(void)
{
	//platform_driver_unregister(&simple_timer_driver);
}

module_init(mol_simple_timer_init);
module_exit(mol_simple_timer_exit);
